﻿/*******************************************************************************************************
 * KinectHelper.cs
 * This class is responsible for the initialization of the kinect sensor, skeletal tracking, postures/gestures/speech recognition and other kinect related events handling
 * *****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Microsoft.Speech.Recognition;
using Kinect.Toolbox;
using Microsoft.Kinect;
using Kinect.Toolbox.Record;
using System.IO;
using Microsoft.Speech.AudioFormat;
using System.Windows.Media.Imaging;

namespace SharePointExplorer.Classes
{
    public class KinectHelper
    {
        #region Declarations

        App app = (App)Application.Current;

        RecognizerInfo ri;
        KinectAudioSource source;
        Grammar g;
        SpeechRecognitionEngine sre;
                

        private static SpeechRecognitionEngine speechRecognizer;
        
        SwipeGestureDetector swipeGestureRecognizer;
        TemplatedGestureDetector circleGestureRecognizer;
        readonly ColorStreamManager colorManager = new ColorStreamManager();
        readonly DepthStreamManager depthManager = new DepthStreamManager();
        AudioStreamManager audioManager;
        SkeletonDisplayManager skeletonDisplayManager;
        readonly BarycenterHelper barycenterHelper = new BarycenterHelper();
        readonly static AlgorithmicPostureDetector algorithmicPostureRecognizer = new AlgorithmicPostureDetector();
        TemplatedPostureDetector templatePostureDetector;
        private bool recordNextFrameForPosture;
        bool displayDepth;
        static SpeechService SpeechSvc = new SpeechService();
        string letterT_KBPath = Path.Combine(Environment.CurrentDirectory, @"data\t_KB.save");
        static KinectAudioSource sensorAudioSource = null;
        const int skeletonCount = 6;
        Skeleton[] allSkeletons = new Skeleton[skeletonCount];
        
        private Skeleton[] skeletons;

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiates the kinect sensor
        /// </summary>
        public KinectHelper()
        {
            try
            {
                //listen to any status change for Kinects
                KinectSensor.KinectSensors.StatusChanged += Kinects_StatusChanged;

                //loop through all the Kinects attached to this PC, and start the first that is connected without an error.
                foreach (KinectSensor kinect in KinectSensor.KinectSensors)
                {
                    if (kinect.Status == KinectStatus.Connected)
                    {
                        app.MainWindowInstance.kinectSensor = kinect;
                        break;
                    }
                }

                if (KinectSensor.KinectSensors.Count == 0) ;
                //MessageBox.Show("No Kinect found");
                else
                    Initialize();

            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.Message);
            }

        }

        #endregion

        #region Methods

        /// <summary>
        /// Initialize the Kinect and assign Event Handlers. Start the sensor.
        /// </summary>
        private void Initialize()
        {
            if (app.MainWindowInstance.kinectSensor == null)
                return;

            app.MainWindowInstance.kinectSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
            app.MainWindowInstance.kinectSensor.ColorFrameReady += kinectRuntime_ColorFrameReady;

            app.MainWindowInstance.kinectSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
            app.MainWindowInstance.kinectSensor.DepthFrameReady += kinectSensor_DepthFrameReady;

            app.MainWindowInstance.kinectSensor.SkeletonStream.Enable(new TransformSmoothParameters
            {
                Smoothing = 0.7f,
                Correction = 0.3f,
                Prediction = 0.4f,
                JitterRadius = 1.0f,
                MaxDeviationRadius = 0.5f
            });


            app.MainWindowInstance.kinectSensor.SkeletonFrameReady += kinectRuntime_SkeletonFrameReady;

            ri = GetKinectRecognizer();

            if (ri == null)
            {
                return;
            }

            swipeGestureRecognizer = new SwipeGestureDetector();

            speechRecognizer = this.CreateSpeechRecognizer();
            sensorAudioSource = app.MainWindowInstance.kinectSensor.AudioSource;
            app.MainWindowInstance.kinectSensor.Start();
        }

        /// <summary>
        /// Creates a SpeechRecognitionEngine and loads the grammar into it.
        /// </summary>
        /// <returns>Instance of the SpeechRecognitionEngine</returns>
        private SpeechRecognitionEngine CreateSpeechRecognizer()
        {
            RecognizerInfo ri = GetKinectRecognizer();
            if (ri == null)
            {
                return null;
            }

            SpeechRecognitionEngine sre;
            try
            {
                sre = new SpeechRecognitionEngine(ri.Id);
            }
            catch
            {
                return null;
            }

            var colors = new Choices();
            colors.Add("kinect_close");
            colors.Add("kinect read");
            

            var gb = new GrammarBuilder { Culture = ri.Culture };
            gb.Append(colors);

            var g = new Grammar(gb);

            sre.LoadGrammar(g);
            sre.SpeechRecognized += this.SreSpeechRecognized;
            sre.SpeechHypothesized += this.SreSpeechHypothesized;
            sre.SpeechRecognitionRejected += this.SreSpeechRecognitionRejected;

            return sre;
        }

        /// <summary>
        /// Stops the kinect audio recognition
        /// </summary>
        public static void Stop()
        {
            if(speechRecognizer!=null)
                speechRecognizer.RecognizeAsyncStop();
        }

        /// <summary>
        /// Starts the kinect audio recognition
        /// </summary>
        public static void Start()
        {
            var audioSource = sensorAudioSource;
            //audioSource.BeamAngleMode = BeamAngleMode.Adaptive;
            var kinectStream = audioSource.Start();
            //this.stream = new EnergyCalculatingPassThroughStream(kinectStream);
            speechRecognizer.SetInputToAudioStream(
                kinectStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));
            speechRecognizer.RecognizeAsync(RecognizeMode.Multiple);

        }
        
        /// <summary>
        /// Dumps the audio stream into a file
        /// </summary>
        /// <param name="audio">Recognized audio stream</param>
        private void DumpRecordedAudio(RecognizedAudio audio)
        {
            if (audio == null)
            {
                return;
            }

            int fileId = 0;
            string filename;
            while (File.Exists((filename = "RetainedAudio_" + fileId + ".wav")))
            {
                fileId++;
            }

            Console.WriteLine("\nWriting file: {0}", filename);
            using (var file = new FileStream(filename, System.IO.FileMode.CreateNew))
            {
                audio.WriteToWaveStream(file);
            }
        }
        
        /// <summary>
        /// Obtains the installled recognizer for Kinect
        /// </summary>
        /// <returns>RecognizerInfo for Kinect</returns>
        private static RecognizerInfo GetKinectRecognizer()
        {
            Func<RecognizerInfo, bool> matchingFunc = r =>
            {
                string value;
                r.AdditionalInfo.TryGetValue("Kinect", out value);
                return "True".Equals(value, StringComparison.InvariantCultureIgnoreCase) && "en-US".Equals(r.Culture.Name, StringComparison.InvariantCultureIgnoreCase);
            };
            return SpeechRecognitionEngine.InstalledRecognizers().Where(matchingFunc).FirstOrDefault();
        }

        /// <summary>
        /// Process each frame that is recorded by the Kinect.
        /// </summary>
        /// <param name="frame">Kinect Recorded Skeletal Frame</param>
        void ProcessFrame(ReplaySkeletonFrame frame)
        {
            Dictionary<int, string> stabilities = new Dictionary<int, string>();
            foreach (var skeleton in frame.Skeletons)
            {
                if (skeleton.TrackingState != SkeletonTrackingState.Tracked)
                    continue;

                barycenterHelper.Add(skeleton.Position.ToVector3(), skeleton.TrackingId);
                if (!barycenterHelper.IsStable(skeleton.TrackingId))
                    return;

                foreach (Joint joint in skeleton.Joints)
                {
                    if (joint.TrackingState != JointTrackingState.Tracked)
                        continue;

                    if (joint.JointType == JointType.HandRight)
                    {
                        //swipeGestureRecognizer.Add(joint.Position, app.MainWindowInstance.kinectSensor);
                        //circleGestureRecognizer.Add(joint.Position, kinectSensor);
                        //circleGestureRecognizer.Add(joint.Position, app.MainWindowInstance.kinectSensor);
                    }
                }

                algorithmicPostureRecognizer.TrackPostures(skeleton);
                
                //templatePostureDetector.TrackPostures(skeleton);

                
                  //  templatePostureDetector.AddTemplate(skeleton);
                //    recordNextFrameForPosture = false;
                //}
            }

        }
        
        /// <summary>
        /// Turns on/off the posture detection
        /// </summary>
        /// <param name="enable">Value to enable or disable the posture detection</param>
        public static void TogglePostureDetectedHandler(bool enable)
        {
            if(enable)
                algorithmicPostureRecognizer.PostureDetected+=new Action<string>(algorithmicPostureRecognizer_PostureDetected);
            else
                algorithmicPostureRecognizer.PostureDetected -= new Action<string>(algorithmicPostureRecognizer_PostureDetected);
        }
        
        /// <summary>
        /// Loads the file to help recognize the "T" Gesture
        /// </summary>
        void LoadLetterTPostureDetector()
        {
            //using (Stream recordStream = File.Open(letterT_KBPath, FileMode.OpenOrCreate))
            //{
            //    templatePostureDetector = new TemplatedPostureDetector("T", recordStream);
            //    templatePostureDetector.PostureDetected += new Action<string>(templatePostureDetector_PostureDetected);
            //}

        }

        /// <summary>
        /// Cleans up the kinect sensor and all the related events.
        /// </summary>
        private void Clean()
        {
            if (swipeGestureRecognizer != null)
            {
                swipeGestureRecognizer.OnGestureDetected -= OnGestureDetected;
            }

            if (audioManager != null)
            {
                audioManager.Dispose();
                audioManager = null;
            }

            //CloseGestureDetector();

            //ClosePostureDetector();

            //if (voiceCommander != null)
            //{
            //    voiceCommander.OrderDetected -= voiceCommander_OrderDetected;
            //    voiceCommander.Dispose();
            //    voiceCommander = null;
            //}

            //if (recorder != null)
            //{
            //    recorder.Stop();
            //    recorder = null;
            //}

            if (app.MainWindowInstance.kinectSensor != null)
            {
                app.MainWindowInstance.kinectSensor.ColorFrameReady -= kinectRuntime_ColorFrameReady;
                app.MainWindowInstance.kinectSensor.SkeletonFrameReady -= kinectRuntime_SkeletonFrameReady;
                app.MainWindowInstance.kinectSensor.ColorFrameReady -= kinectRuntime_ColorFrameReady;
                app.MainWindowInstance.kinectSensor.Stop();
                app.MainWindowInstance.kinectSensor = null;
            }
        }

        #endregion

        #region EventHandlers

        /// <summary>
        /// Handles the kinect status change event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Kinects_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            switch (e.Status)
            {
                case KinectStatus.Connected:
                    if (app.MainWindowInstance.kinectSensor == null)
                    {
                        app.MainWindowInstance.kinectSensor = e.Sensor;
                        Initialize();
                    }
                    break;
                case KinectStatus.Disconnected:
                    if (app.MainWindowInstance.kinectSensor == e.Sensor)
                    {
                        Clean();
                        //MessageBox.Show("Kinect was disconnected");
                    }
                    break;
                case KinectStatus.NotReady:
                    break;
                case KinectStatus.NotPowered:
                    if (app.MainWindowInstance.kinectSensor == e.Sensor)
                    {
                        Clean();
                        //MessageBox.Show("Kinect is no more powered");
                    }
                    break;
                default:
                    //MessageBox.Show("Unhandled Status: " + e.Status);
                    break;
            }
        }

        /// <summary>
        /// Handles the speech rejection event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SreSpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            //MessageBox.Show(e.Result.Text.ToString() + " Speech Rejected");
            // Console.WriteLine("\nSpeech Rejected");
            if (e.Result != null)
            {
                DumpRecordedAudio(e.Result.Audio);
            }
        }

        /// <summary>
        /// Handles the speech hypothesized event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SreSpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            Console.Write("\rSpeech Hypothesized: \t{0}", e.Result.Text);
            // //MessageBox.Show(e.Result.Text.ToString()+" Speech hypothized");
            //if (e.Result.Text == "close")
            //Environment.Exit(0);
        }

        /// <summary>
        /// Handles the speech recognition event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SreSpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            if (e.Result.Confidence >= 0.5 && e.Result.Text == "kinect read")
            {
                ////MessageBox.Show(e.Result.Text.ToString());
                SpeechService svc = new SpeechService();
                svc.ReadText(app.TextToRead);
                speechRecognizer.RecognizeAsyncStop();
                //Console.WriteLine("\nSpeech Recognized: \t{0}\tConfidence:\t{1}", e.Result.Text, e.Result.Confidence);
            }

        }

        /// <summary>
        /// Handles the gesture detection event
        /// </summary>
        /// <param name="obj"></param>
        void swipeGestureRecognizer_OnGestureDetected(string obj)
        {
            //NavigateContent(new SharePointLocator(), new Dashboard());
            //MessageBox.Show(obj);
        }

        /// <summary>
        /// Handles the depth frame capture ready event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            if (!displayDepth)
                return;

            using (var frame = e.OpenDepthImageFrame())
            {
                if (frame == null)
                    return;

                depthManager.Update(frame);
            }
        }

        /// <summary>
        /// handles the color frame ready event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinectRuntime_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            if (displayDepth)
                return;

            using (var frame = e.OpenColorImageFrame())
            {
                if (frame == null)
                    return;

                colorManager.Update(frame);
            }
        }

        /// <summary>
        /// handles the skeleton frame ready event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void kinectRuntime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            using (SkeletonFrame frame = e.OpenSkeletonFrame())
            {
                if (frame == null)
                    return;

                //if (recorder != null)
                //    recorder.Record(frame);

                Tools.GetSkeletons(frame, ref skeletons);

                if (skeletons.All(s => s.TrackingState == SkeletonTrackingState.NotTracked))
                    return;

                ProcessFrame(frame);

            }
        }

        /// <summary>
        /// handles the posture recognition event for the algorithmic posture recognizer
        /// </summary>
        /// <param name="obj"></param>
        static void algorithmicPostureRecognizer_PostureDetected(string obj)
        {
            App app = (App)Application.Current;
            app.MainWindowInstance.DeactivateButton.IsEnabled = true;
            switch (obj)
            {
                case "LeftHandOverHead":
                    if (!SharePoint.SharePointUtils.DeleteFlag)
                    {
                        SharePoint.SharePointUtils.DeleteFlag = true;
                        app.MainWindowInstance.DeactivateButton.IsEnabled = true;
                        app.MainWindowInstance.imgDeactivate.Source = new BitmapImage(new Uri("/SharePointExplorer;component/Images/Deactivate.png", UriKind.Relative));
                        SpeechSvc.SpeakOperation(SpeechService.Operation.Delete, SpeechService.OperationType.Before);
                    }
                    break;


                case "LeftHello":
                    if (!SharePoint.SharePointUtils.MoveFlag)
                    {
                        SharePoint.SharePointUtils.MoveFlag = true;
                        app.MainWindowInstance.DeactivateButton.IsEnabled = true;
                        app.MainWindowInstance.imgDeactivate.Source = new BitmapImage(new Uri("/SharePointExplorer;component/Images/Deactivate.png", UriKind.Relative));
                        SpeechSvc.SpeakOperation(SpeechService.Operation.Move, SpeechService.OperationType.Before);
                    }
                    break;

                case "HandsJoined":
                    if (!SharePoint.SharePointUtils.CopyFlag)
                    {
                        SharePoint.SharePointUtils.CopyFlag = true;
                        app.MainWindowInstance.DeactivateButton.IsEnabled = true;
                        app.MainWindowInstance.imgDeactivate.Source = new BitmapImage(new Uri("/SharePointExplorer;component/Images/Deactivate.png", UriKind.Relative));
                        SpeechSvc.SpeakOperation(SpeechService.Operation.Copy, SpeechService.OperationType.Before);
                    }
                    break;

                case "RightHello":
                    //SharePoint.SharePointUtils.CopyFlag = false;
                    //SharePoint.SharePointUtils.MoveFlag = false;
                    //SharePoint.SharePointUtils.DeleteFlag = false;
                    break;

            }

        }

        /// <summary>
        /// handles posture recognition event for template posture detector
        /// </summary>
        /// <param name="obj"></param>
        void templatePostureDetector_PostureDetected(string obj)
        {

        }

        /// <summary>
        /// handles the gesture detected event
        /// </summary>
        /// <param name="gesture"></param>
        void OnGestureDetected(string gesture)
        {
            //int pos = detectedGestures.Items.Add(string.Format("{0} : {1}", gesture, DateTime.Now));

            //detectedGestures.SelectedIndex = pos;
            ////MessageBox.Show("Circle");


        }

        #endregion


    }
}
